#include "stdafx.h"
#include "Renderer.h"

using namespace Rendering;

float Renderer::clearColor[] = { 229.0f/255, 228.0f/255, 229.0f/255, 1.0f };

/// <summary>Constructor.</summary>
Renderer::Renderer() 
	: m_bufferCount(2)
	, m_initialized(false)
{
}

/// <summary>Initializes the Direct3D resources required to run.</summary>
void Renderer::Initialize()
{
    if (m_initialized)
    {
        // Being restarted probably as a result of the device was removed either by a disconnect
        // or a driver upgrade TDR so make sure the rendering state has been released.
        m_renderTargetView = nullptr;
        m_depthStencilView = nullptr;
        m_swapChain = nullptr;
    }

    CreateDeviceIndependentResources();
    CreateStaticResources();

    m_initialized = true;
}

/// <summary>Initializes the resources required independent of hardware.</summary>
void Renderer::CreateDeviceIndependentResources()
{
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
     // If the project is in a debug build, enable Direct2D debugging via SDK Layers
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
}

/// <summary>Initializes the resources dependent of hardware.</summary>
void Renderer::CreateStaticResources()
{
    // This flag adds support for surfaces with a different color channel ordering than the API default.
    // It is recommended usage, and is required for compatibility with Direct2D.
    UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    IDXGIDevicePtr dxgiDevice;

#if defined(_DEBUG)
    // If the project is in a debug build, enable debugging via SDK Layers with this flag.
    creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    // This array defines the set of DirectX hardware feature levels this app will support.
    // Note the ordering should be preserved.
    // Don't forget to declare your application's minimum required feature level in its
    // description.  All applications are assumed to support 9.1 unless otherwise stated.
    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
    };

    // Create the DX11 API device object, and get a corresponding context.
    ID3D11DevicePtr device;
    ID3D11DeviceContextPtr context;
    IF_FAILED_THROW_HR(
        D3D11CreateDevice(
            nullptr,                    // specify null to use the default adapter
			D3D_DRIVER_TYPE_HARDWARE,
            0,                          // leave as 0 unless software device
            creationFlags,              // optionally set debug and Direct2D compatibility flags
            featureLevels,              // list of feature levels this app can support
            ARRAYSIZE(featureLevels),   // number of entries in above list
            D3D11_SDK_VERSION,          // always set this to D3D11_SDK_VERSION for AppStore style apps
            &device,                    // returns the Direct3D device created
            &m_featureLevel,            // returns feature level of device created
            &context                    // returns the device immediate context
            )
        );

    // Get the DirectX11.1 device by QI off the DirectX11 one.
    m_device = device;

    // And get the corresponding device context in the same way.
    m_context = context;

    // Obtain the underlying DXGI device of the Direct3D11.1 device.
    dxgiDevice = m_device;

    // Release the swap chain (if it exists) as it will be incompatible with the new device.
    m_swapChain = nullptr;
}

/// <summary>Creates the resources which needs to be updated for the attached window update.</summary>
void Renderer::CreateRenderTargetResources()
{
    // If the swap chain already exists, resize it.
    if (m_swapChain != nullptr)
    {
        // Existing swap chain needs to be resized
        // make sure that the depend objects have been released.
        m_context->OMSetRenderTargets(0, nullptr, nullptr);
        m_renderTargetView = nullptr;
        m_depthStencilView = nullptr;

        IF_FAILED_THROW_HR(
            m_swapChain->ResizeBuffers(
                m_bufferCount,
                static_cast<UINT>(m_renderTargetSize.width),
                static_cast<UINT>(m_renderTargetSize.height),
                DXGI_FORMAT_B8G8R8A8_UNORM,
                0
                )
            );
    }
    else    // Otherwise, create a new one.
    {
       // m_swapChain is nullptr either because it has never been created or because it has been
        // invalidated. Make sure that the dependent objects are also released.
        m_context->OMSetRenderTargets(0, nullptr, nullptr);
        m_renderTargetView = nullptr;
        m_depthStencilView = nullptr;
        m_context->Flush();


        // Allocate a descriptor.
        DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
        swapChainDesc.Width = static_cast<UINT>(m_renderTargetSize.width); // Match the size of the window.
        swapChainDesc.Height = static_cast<UINT>(m_renderTargetSize.height);
        swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;           // this is the most common swapchain format
        swapChainDesc.Stereo = false;
        swapChainDesc.SampleDesc.Count = 1;                          // don't use multi-sampling
        swapChainDesc.SampleDesc.Quality = 0;
        swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        swapChainDesc.BufferCount = 2;                               // use double buffering to enable flip
        swapChainDesc.Scaling = DXGI_SCALING_STRETCH;                // Required to be STRETCH for Composition
        swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all AppStore apps must use this SwapEffect
        swapChainDesc.Flags = 0;

        // Once the desired swap chain description is configured, it must be created on the same adapter as our D3D Device

        // First, retrieve the underlying DXGI Device from the D3D Device.
        IDXGIDevice1Ptr dxgiDevice = m_device;

        // Identify the physical adapter (GPU or card) this device is running on.
        IDXGIAdapterPtr dxgiAdapter;
        IF_FAILED_THROW_HR(dxgiDevice->GetAdapter(&dxgiAdapter));

        // And obtain the factory object that created it.
        IDXGIFactory2Ptr dxgiFactory;
        IF_FAILED_THROW_HR(dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)));

		CreateSwapChain(dxgiFactory, swapChainDesc);

        // Ensure that DXGI does not queue more than (m_bufferCount - 1) at a time. This both reduces
        // latency and ensures that the application will only render after each VSync, minimizing
        // power consumption.
        IF_FAILED_THROW_HR(dxgiDevice->SetMaximumFrameLatency(m_bufferCount - 1));
    }

    // Obtain the backbuffer for this window which will be the final 3D rendertarget.
    ID3D11Texture2DPtr backBuffer;
    IF_FAILED_THROW_HR(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)));

    // Create a descriptor for the RenderTargetView.
    CD3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc(D3D11_RTV_DIMENSION_TEXTURE2DARRAY, DXGI_FORMAT_B8G8R8A8_UNORM, 0, 0, 1);

    // Create a view interface on the rendertarget to use on bind for mono or left eye view.
    IF_FAILED_THROW_HR(m_device->CreateRenderTargetView(backBuffer, &renderTargetViewDesc, &m_renderTargetView));

    // Create a descriptor for the depth/stencil buffer.
	CD3D11_TEXTURE2D_DESC depthBufferDesc(DXGI_FORMAT_D24_UNORM_S8_UINT, static_cast<UINT>(m_renderTargetSize.width), static_cast<UINT>(m_renderTargetSize.height),
        1, 1, D3D11_BIND_DEPTH_STENCIL);

    // Allocate a 2-D surface as the depth/stencil buffer.
    ID3D11Texture2DPtr depthStencilBuffer;
    IF_FAILED_THROW_HR(m_device->CreateTexture2D( &depthBufferDesc, nullptr,  &depthStencilBuffer));

    // Create a DepthStencil view on this surface to use on bind.
    CD3D11_DEPTH_STENCIL_VIEW_DESC viewDesc = CD3D11_DEPTH_STENCIL_VIEW_DESC(D3D11_DSV_DIMENSION_TEXTURE2D);
    IF_FAILED_THROW_HR(m_device->CreateDepthStencilView(depthStencilBuffer, &viewDesc, &m_depthStencilView));

	UpdateViewport(D2D1::RectF(0.0f, 0.0f, m_renderTargetSize.width, m_renderTargetSize.height));
}


/// <summary>Delivers the final image to the display.</summary>
void Renderer::Present()
{
    // The application may optionally specify "dirty" or "scroll" rects to improve efficiency
    // in certain scenarios.  In this sample, however, we do not utilize those features.
    DXGI_PRESENT_PARAMETERS parameters = {0};
    parameters.DirtyRectsCount = 0;
    parameters.pDirtyRects = nullptr;
    parameters.pScrollRect = nullptr;
    parameters.pScrollOffset = nullptr;

    // The first argument instructs DXGI to block until VSync, putting the application
    // to sleep until the next VSync. This ensures we don't waste any cycles rendering
    // frames that will never be displayed to the screen.
    HRESULT hr = m_swapChain->Present1(1, 0, &parameters);

    // If the device was removed either by a disconnect or a driver upgrade, we
    // must completely reinitialize the renderer.
    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
    {
        Initialize();
    }
    else
    {
        IF_FAILED_THROW_HR(hr);
    }
}

/// <summary>Clears the viewport and fills it with clearColor.</summary>
void Renderer::Clear()
{
	// Clear the render target and depth stencil to default values.
	m_context->ClearRenderTargetView(m_renderTargetView,  clearColor );
	m_context->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
}

/// <summary>Updates the render target size.</summary>
/// <param name="renderTargetSize">The new render target size.</param>
void Renderer::UpdateRenderTargetSize(D2D1_SIZE_F renderTargetSize)
{
	bool sizeChanged = (renderTargetSize.width != m_renderTargetSize.width ||
       renderTargetSize.height != m_renderTargetSize.height);
	// Calculate the necessary swap chain and render target size in pixels.
	m_renderTargetSize.width = renderTargetSize.width;
	m_renderTargetSize.height = renderTargetSize.height;
	if (m_initialized && sizeChanged)
    {
        CreateRenderTargetResources();
    }
}

/// <summary>Updates the render with a new view port.</summary>
/// <param name="viewPort">The new view port.</param>
void Renderer::UpdateViewport(D2D1_RECT_F viewPort)
{
	// Create a viewport descriptor of the full window size.
    CD3D11_VIEWPORT viewport(
		viewPort.left,
		viewPort.top,
		viewPort.right - viewPort.left,
        viewPort.bottom - viewPort.top
        );

    // Set the current viewport using the descriptor.
    m_context->RSSetViewports(1, &viewport);

	m_viewPort = viewPort;
}

/// <summary>Turns on alpha blending.</summary>
void Renderer::TurnOnAlphaBlending()
{
	float blendFactor[4];

	// Setup the blend factor.
	blendFactor[0] = 0.0f;
	blendFactor[1] = 0.0f;
	blendFactor[2] = 0.0f;
	blendFactor[3] = 0.0f;
	
	// Turn on the alpha blending.
	m_context->OMSetBlendState(m_alphaEnableBlendingState, blendFactor, 0xffffffff);
}

/// <summary>Turns off alpha blending.</summary>
void Renderer::TurnOffAlphaBlending()
{
	float blendFactor[4];
	

	// Setup the blend factor.
	blendFactor[0] = 0.0f;
	blendFactor[1] = 0.0f;
	blendFactor[2] = 0.0f;
	blendFactor[3] = 0.0f;
	
	// Turn off the alpha blending.
	m_context->OMSetBlendState(m_alphaDisableBlendingState, blendFactor, 0xffffffff);
}